package com.playtech.tournament.balancing.test.client;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.event.dom.client.ClickEvent;
import com.google.gwt.event.dom.client.ClickHandler;
import com.google.gwt.user.client.ui.Button;
import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Label;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.ScrollPanel;
import com.google.gwt.user.client.ui.TextBox;

public class Balancing implements EntryPoint {

	private static FlowPanel log = new FlowPanel();
	static ScrollPanel sp = new ScrollPanel();
	static TextBox registeredPlayersTxt = new TextBox();
	static ListBox seatsLst = new ListBox();
	static ArrayList<TableStub> tablesArr = new ArrayList<TableStub>();
	static int lateCounter = 1;
	static TableStub notStartedTableForLR = null;

	RootPanel params;
	RootPanel tables = RootPanel.get("tables");
	int tablesNumber;

	/**
	 * This is the entry point method.
	 */
	public void onModuleLoad() {
		final Label tableLbl = new Label("Registered Players:");
		final Label seatsLbl = new Label("Players per Table:");
		registeredPlayersTxt.setText("33");
		for (int i = 2; i < 11; i++) {
			seatsLst.addItem("" + i, "" + i);
		}
		seatsLst.setSelectedIndex(seatsLst.getItemCount() - 1);

		final Button createTablesButton = new Button("Create");
		final Button addLaterRegistrant = new Button("Add Late Registrant");

		createTablesButton.addStyleName("sendButton");
		addLaterRegistrant.addStyleName("sendButton");

		// Add the nameField and sendButton to the RootPanel
		// Use RootPanel.get() to get the entire body element
		params = RootPanel.get("params");

		RootPanel l = RootPanel.get("log");

		l.add(sp);
		sp.add(log);
		sp.setHeight("200px");

		params.add(tableLbl);
		tableLbl.setStyleName("floatLeft");
		registeredPlayersTxt.setStyleName("floatLeft");
		params.add(registeredPlayersTxt);
		params.add(seatsLbl);
		seatsLbl.setStyleName("floatLeft");
		params.add(seatsLst);
		seatsLst.setStyleName("floatLeft");
		params.add(createTablesButton);
		createTablesButton.addStyleName("floatLeft");
		params.add(addLaterRegistrant);
		addLaterRegistrant.addStyleName("floatLeft");
		
		registeredPlayersTxt.setFocus(true);
		registeredPlayersTxt.selectAll();
		createTablesButton.addClickHandler(createTables);
		addLaterRegistrant.addClickHandler(addLateClickHandler);
	}

	ClickHandler addLateClickHandler = new ClickHandler() {

		public void onClick(ClickEvent event) {
			sortTables();
			int playersPerTable = getSeatsPerTable();
			
			Player player = new Player("Late-"+lateCounter++);
			
			TableStub tableWithSmallestNumberOfPlayers = tablesArr.get(0);
			if (tableWithSmallestNumberOfPlayers.countPlayers() < playersPerTable)
			{
				//sit player on this table
				tableWithSmallestNumberOfPlayers.seatPlayer(player, false);
				
				//check if game is stopped 
				if (tableWithSmallestNumberOfPlayers.getIsPausedWithSinglePlayer())
				{
					tableWithSmallestNumberOfPlayers.startGame();
				}
				else if (tableWithSmallestNumberOfPlayers.getIsPaused())
				{
					//if play on this table has never been started - we will start it only after it is almost balanced
					int minPlayersAtTable = countPlayers()/tablesArr.size();
					if (minPlayersAtTable - tableWithSmallestNumberOfPlayers.countPlayers() <= 1)
					{
						tableWithSmallestNumberOfPlayers.startGame();
						assert(tableWithSmallestNumberOfPlayers == notStartedTableForLR);
						notStartedTableForLR = null;
					}
				}
			}
			else
			{
				//create new table and sit player there
				TableStub table = new TableStub("" + (tablesNumber++), playersPerTable, 0);
				tablesArr.add(table);
				tables.add(table);
				
				notStartedTableForLR = table;
				
				table.seatPlayer(player, false);
			}
		}
	};

	ClickHandler createTables = new ClickHandler() {

		public void onClick(ClickEvent event) {
			tables.clear();
			tablesArr.clear();
			log.clear();
			lateCounter = 1;
			int registered = Integer.valueOf(registeredPlayersTxt.getText());
			int seats = getSeatsPerTable();
			tablesNumber = registered / getSeatsPerTable()
					+ (registered % getSeatsPerTable() > 0 ? 1 : 0);
			int players = registered;
			
			int playersPerTable = registered / tablesNumber;
			int overFill = registered - tablesNumber*playersPerTable;
			
			for (int i = 0; i < tablesNumber; i++) {
				TableStub w = new TableStub("" + i, seats, playersPerTable + (overFill > 0 ? 1 : 0));
				players -= playersPerTable + (overFill > 0 ? 1 : 0);
				if (overFill > 0) overFill = overFill - 1;
				
				w.startGame();				

				tablesArr.add(w);
				tables.add(w);
			}
		}
	};

	private static int getSeatsPerTable() {
		return Integer
				.valueOf(seatsLst.getItemText(seatsLst.getSelectedIndex()));
	}

	public static boolean checkBalanceTables(TableStub source) {
		String hdr = "Tournament.checkBalanceTables(" + source.getName()
				+ "); ";
		log("Entering with table " + source.getName() + ".");

		int playersPerTable = getSeatsPerTable();
		boolean balancingNeeded = false;

		int numTables = tablesArr.size();
		if (numTables < 2) {
			log("table " + source.getName() + " Leaving, numTables < 2.");
			return false; // Nothing to balance
		}

		int numPlayers = countPlayers();
		int sourcePlayerCount = source.countPlayers();
		int minPlayersAtTable = numPlayers / numTables;

		log(hdr + "numTables=" + numTables + ", playerCount = " + numPlayers
				+ "; sourcePlayerCount = " + sourcePlayerCount
				+ "; minPlayersAtTable = " + minPlayersAtTable);

		if (numPlayers <= (numTables - 1) * playersPerTable) {
			log(hdr + "Must breakdown table.");
			balancingNeeded = true;

		} else {
			balancingNeeded = sourcePlayerCount > minPlayersAtTable;

		}

		if (!balancingNeeded)
			return false;

		log(hdr + "Balancing required.");
		// source.startCollectingUpdates();
		balanceTables(source);
		// source.sendCollectedUpdates();

		// Bug 22953 (nigul)
		// if (source.isHandFinishedEventBeingProcessed()) {
		// postProcessHandFinished(source);
		// }

		return true;
	}

	static int countPlayers() {
		int ret = 0;
		for (TableStub stub : tablesArr) {
			ret += stub.countPlayers();
		}
		return ret;
	}

	private static void sortTables() {
		Collections.sort(tablesArr, new Comparator<TableStub>() {
			public int compare(TableStub o1, TableStub o2) {
				return o1.countPlayers() - o2.countPlayers();
			};
		});
	}
	static protected void balanceTables(TableStub source) {

		String hdr = "Tournament.balanceTables(" + source.getName() + "); ";

		sortTables();
		
		int numTables = tablesArr.size();
		int playersPerTable = getSeatsPerTable();

		if (numTables < 2) {
			log(hdr + "Leaving, numTables < 2.");
			return; // Nothing to balance
		}

		int overFill = 0;
		boolean forceMove = false;
		int numPlayers = countPlayers();
		int sourcePlayerCount = source.countPlayers();
		log(hdr + "numTables=" + numTables + ", numPlayers=" + numPlayers
				+ ", sourcePlayerCount=" + sourcePlayerCount + ".");

		int minPlayersAtTable = numPlayers / numTables;

		int numPlayersToMove = 0;
		if (numPlayers <= (numTables - 1) * playersPerTable) {
			// if there is a table with gameplay not started - break it
			if (notStartedTableForLR != null && notStartedTableForLR != source)
			{
				log (hdr + "there is not started table, will break it instead of current.");
				balanceTables(notStartedTableForLR);
				return; //no balancing on the current table, just the LR table
			}
			
			// TableStub source should shut down
			minPlayersAtTable = numPlayers / (numTables - 1);
			numPlayersToMove = sourcePlayerCount;
			overFill = numPlayers - (numTables - 1) * minPlayersAtTable;
		} else {
			int playersAtSmallestTable = tablesArr.get(0).countPlayers();
			boolean underfilledTable = playersAtSmallestTable < minPlayersAtTable;

			if (numPlayers % numTables > 0) {
				// we have tables with more than minPlayerAtTable
				// if we have underfilled table we need to get as much players
				// from this table as possible
				numPlayersToMove = (sourcePlayerCount - minPlayersAtTable)
						- (underfilledTable ? 0 : 1);
				forceMove = true; // if necessary we will put players on tables
									// with minPlayersAtTable count
			} else {
				numPlayersToMove = (sourcePlayerCount - minPlayersAtTable);
			}

		}

		log(hdr + "Movement calculations done: minPlayersAtTable="
				+ minPlayersAtTable + ", numPlayersToMove=" + numPlayersToMove
				+ ".");

		if (numPlayersToMove < 1) {
			log(hdr + "Leaving, numPlayersToMove < 1.");
			return; // Nothing to do.
		}

		log(hdr + "Starting iteration over sorted tables.");
		Iterator<TableStub> tableIter = tablesArr.iterator();
		while (numPlayersToMove > 0 && tableIter.hasNext()) {
			log(hdr + "Iteration step, players left to move="
					+ numPlayersToMove + ".");
			TableStub target = tableIter.next();
			log(hdr + "Iteration step, target=" + target.getName() + ".");

			if (target == source) {
				log(hdr + "Skip to next, target=source.");
				continue;
			}

			int targetPlayerCount = target.countPlayers();

			log(hdr + "Iteration step, targetPlayerCount=" + targetPlayerCount
					+ ".");
			if (targetPlayerCount > minPlayersAtTable) {
				log(hdr
						+ "Skip to next, targetPlayerCount > minPlayersAtTable.");
				continue;
			}

			if (targetPlayerCount == minPlayersAtTable) {
				if (!(overFill > 0 || forceMove)) {
					log(hdr
							+ "Skip to next, targetPlayerCount == minPlayersAtTable, no overfill.");
					continue;
				}

				if (targetPlayerCount == playersPerTable) {
					log(hdr + "Skip to next, targetPlayerCount == "
							+ playersPerTable + ".");
					continue;
				}
			}

			log(hdr
					+ "Will move "
					+ (minPlayersAtTable - targetPlayerCount + (overFill > 0
							|| forceMove ? 1 : 0)) + " players.");
			int firstPosition = 0;
			if (overFill > 0) {
				firstPosition = -1;
				overFill--;
			}

			if (forceMove) {
				firstPosition = -1;
			}

			for (int ctr = firstPosition; ctr < minPlayersAtTable
					- targetPlayerCount; ctr++) {
				movePlayer(source, target);

				numPlayersToMove--;
				if (numPlayersToMove < 1)
					break;
			}
			// if hands are not played on the target table
			// because there was only 1 player
			// then start the gameplay now
			// if (target.isPausedWithSinglePlayer() && !isOnBreak())
			// startGame(target);
			
			if (target.getIsPausedWithSinglePlayer())
			{
				target.startGame();
			}
			else if (target.getIsPaused())
			{
				//if play on this table has never been started - we will start it only after it is almost balanced
				if (minPlayersAtTable - target.countPlayers() <= 1)
				{
					target.startGame();
					assert(target == notStartedTableForLR);
					notStartedTableForLR = null;
				}
			}
		}
		log(hdr + "Iteration done.");

		if (source.countPlayers() == 0) {
			log(hdr + "No players left at source, shutting down.");
			// Request no. 11292, task no. 45008
			shutdownTable(source);

		}

		log(hdr + "Leaving.");

	}

	static private void shutdownTable(
			com.playtech.tournament.balancing.test.client.TableStub source) {

		tablesArr.remove(source);
		source.close();
	}

	static private void movePlayer(
			com.playtech.tournament.balancing.test.client.TableStub source,
			com.playtech.tournament.balancing.test.client.TableStub target) {

		Player player = source.removeFirstPlayer();
		if (player == null)
			return;
		log("Moving player:" + player.getName() + " from table:"
				+ source.getName() + " to table:" + target.getName());
		target.seatPlayer(player, false);
		// sendMessage(player.getUser(), COMMAND_POKERSERVER_CHANGETABLE,
		// PokerException.OK, source.getCode(), target.getCode());
	}

	public static void log(String msg) {
		log.add(new Label(msg));
		sp.scrollToBottom();
	}
}
